/*
 * $Id: MemoryPool.cc,v 1.4 2006-04-24 09:01:18 bacon Exp $
 */

#include <xpp/bas/MemoryPool.h>

namespace xpp
{
	namespace bas 
	{

		MemoryPool::MemoryPool (xp_size_t datum_size, xp_size_t block_size)
		{
			if (datum_size > 0 && 
			    datum_size < xp_sizeof(void*)) datum_size = xp_sizeof(void*);

			this->mp_blocks  = XP_NULL;
			this->free_list  = XP_NULL;
			this->datum_size = datum_size;
			this->block_size = block_size;
#ifdef XP_DEBUG_VERBOSE
			this->navail = 0;
			this->nalloc = 0;
#endif
		}
		MemoryPool::~MemoryPool ()
		{
			dispose ();
		}

		void* MemoryPool::allocate ()
		{
			if (this->datum_size <= 0 ||
			    this->block_size <= 0) return XP_NULL;

			void* ptr = this->free_list;
			if (ptr == XP_NULL) {
				add_block ();
				ptr = this->free_list;	
			}
			this->free_list = this->free_list->next;
#ifdef XP_DEBUG_VERBOSE
			this->navail--;
#endif
			return ptr;
		}

		void MemoryPool::dispose (void* ptr)
		{
			((Chain*)ptr)->next = this->free_list;
			this->free_list = (Chain*)ptr;
#ifdef XP_DEBUG_VERBOSE
			this->navail++;
#endif
		}

		void MemoryPool::dispose ()
		{
			Block* block = this->mp_blocks;
			while (block != XP_NULL) {
				Block* next = block->next;
				::delete[] block;
				block = next;
			}

			this->free_list = XP_NULL;
			this->mp_blocks = XP_NULL;
#ifdef XP_DEBUG_VERBOSE
			this->navail = 0;
			this->nalloc = 0;
#endif
		}
		
		void MemoryPool::add_block ()
		{
			xp_assert (this->datum_size > 0 && this->block_size > 0);

			Block* block = (Block*)::new xp_byte_t[
				xp_sizeof(Block) + this->block_size * this->datum_size];
			
			//this->free_list = (Chain*)block->data;
			this->free_list = (Chain*)(block + 1);
			Chain* ptr = this->free_list;
			for (xp_size_t i = 0; i < this->block_size - 1; i++) {
				Chain* next = (Chain*)((xp_byte_t*)ptr + this->datum_size);
				ptr->next = next;
				ptr = next;
			}
			ptr->next = XP_NULL;

			block->next  = this->mp_blocks;
			this->mp_blocks = block;
#ifdef XP_DEBUG_VERBOSE
			this->navail += this->block_size;
			this->nalloc += this->block_size;
#endif
		}

	}
}

